/*
 * amd32.c: AMD 32 bit (2x 16 bit) flash driver
 *
 * Copyright (C) 2001  Erik Mouw (J.A.K.Mouw@its.tudelft.nl)
 * Copyright (C) 2001  Russ Dill (Russ.Dill@asu.edu)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#ident "$Id: amd32.c,v 1.4 2002/01/02 01:23:18 erikm Exp $"

#ifdef HAVE_CONFIG_H
# include <blob/config.h>
#endif

#include <blob/errno.h>
#include <blob/flash.h>
#include <blob/util.h>
#include <blob/serial.h>


/* flash commands for two 16 bit AMD flash chips */
#define READ_ARRAY		0x00F000F0
#define UNLOCK1			0x00AA00AA
#define UNLOCK2			0x00550055
#define ERASE_SETUP		0x00800080
#define ERASE_CONFIRM		0x00300030
#define PGM_SETUP		0x00A000A0
#define UNLOCK_BYPASS		0x00200020
#define FLASH_ADDR1		(0x00000555 << 2)
#define FLASH_ADDR2		(0x000002AA << 2)
#define ERASE_DONE		0x00800080
#define RDY_MASK		0x00800080
#define STATUS_PGM_ERR		0x00200020
#define STATUS_ERASE_ERR	0x00000001

#define READY 1
#define ERR   2




static int flash_erase_amd32(u32 *addr)
{
	u32 result;
	int chip1, chip2;

	/* prepare for erase */
	*(u32 *)FLASH_ADDR1 = data_to_flash(UNLOCK1);
	barrier();
	*(u32 *)FLASH_ADDR2 = data_to_flash(UNLOCK2);
	barrier();
	*(u32 *)FLASH_ADDR1 = data_to_flash(ERASE_SETUP);
	barrier();

	/* erase command */
	*(u32 *)FLASH_ADDR1 = data_to_flash(UNLOCK1);
	barrier();
	*(u32 *)FLASH_ADDR2 = data_to_flash(UNLOCK2);
	barrier();
	*addr = data_to_flash(ERASE_CONFIRM);

	/* I just can't find clean ways of dealing with this flash...
	 * The error bit is a *set* bit, so if its read, and bit 7 is 0,
	 * but bit 5 is 1, its an error, however, after these status reads
	 * are done, erased flash goes to 0xff...sooo...each chip has to
	 * be caught where the bits are the status bits -- Russ */

	/* Russ, why don't you do this like the LART does? Just check
         * the status of chips with a single compare. -- Erik */
	chip1 = chip2 = 0;

	do {
		result = data_from_flash(*addr);
		barrier();

		if (!chip1 && (result & 0xFFFF) & ERASE_DONE)
			chip1 = READY;

		if (!chip1 && (result & 0xFFFF) & STATUS_PGM_ERR) 
			chip1 = ERR;

		if (!chip2 && (result >> 16) & ERASE_DONE)
			chip2 = READY;

		if (!chip2 && (result >> 16) & STATUS_PGM_ERR)
			chip2 = ERR;
		
	} while(!chip1 || !chip2);
	
	/* put flash back into Read Array mode */
	*(u32 *)FLASH_ADDR1 = data_to_flash(READ_ARRAY);
	barrier();
	
	if (chip1 == ERR || chip2 == ERR)
		return -EFLASHERASE;

	return 0;
}




static int flash_write_amd32(u32 *dst, const u32* src)
{
	u32 result;
	int chip1, chip2;

	*(u32 *)FLASH_ADDR1 = data_to_flash(UNLOCK1);
	barrier();
	*(u32 *)FLASH_ADDR2 = data_to_flash(UNLOCK2);
	barrier();
	*(u32 *)FLASH_ADDR1 = data_to_flash(UNLOCK_BYPASS);
	barrier();

	*dst = data_to_flash(PGM_SETUP);
	barrier();
	*dst = *src;
	barrier();
	
	/* This is a pretty similar situation to the erasing status below
	 * Bit 7 is ~(data bit 7) until the flash is complete. If bit 5
	 * gets set before this happens, there is an error, but this could
	 * happen near the clock edge, and bit 5 could be the actual data 
	 * before bit 7 changes, so we have to read again. -- Russ
	 */ 

	chip1 = chip2 = 0;
	do {
		result = data_from_flash(*dst);
		barrier();

		if (!chip1 && ((result & 0x80) == (*src & 0x80))) 
			chip1 = READY;

		if (!chip1 && ((result & 0xFFFF) & STATUS_PGM_ERR)) {
			result = data_from_flash(*dst);
			barrier();

			if ((result & 0x80) == (*src & 0x80))
				chip1 = READY;
			else 
				chip1 = ERR;
		}

		if (!chip2 && ((result & (0x80 << 16)) == (*src & (0x80 << 16))))
			chip2 = READY;

		if (!chip2 && ((result >> 16) & STATUS_PGM_ERR)) {
			result = data_from_flash(*dst);
			barrier();

			if ((result & (0x80 << 16)) == (*src & (0x80 << 16)))
				chip2 = READY;
			else 
				chip2 = ERR;
		}

	} while (!chip1 || !chip2);
		
	*(u32 *)FLASH_ADDR1 = data_to_flash(READ_ARRAY);
	barrier();

	if (chip1 == ERR || chip2 == ERR || *dst != *src)
		return -EFLASHPGM;

	return 0;
}




static int flash_lock_block_amd32(u32 *blockStart)
{
	/* FIXME: if AMD flash can be locked, this function should be
	 * fleshed out -- Erik
	 */
	return 0;
}




static int flash_unlock_block_amd32(u32 *blockStart)
{
	/* FIXME: if AMD flash can be unlocked, this function should
	 * be fleshed out -- Erik
	 */
	return 0;
}




static int flash_query_block_lock_amd32(u32 *blockStart)
{
	/* FIXME: if AMD flash can be queried, this function should be
	 * fleshed out -- Erik
	 */
	return 0;
}




/* flash driver structure */
flash_driver_t amd32_flash_driver = {
	erase:			flash_erase_amd32,
	write:			flash_write_amd32,
	lock_block:		flash_lock_block_amd32,
	unlock_block:		flash_unlock_block_amd32,
	query_block_lock:	flash_query_block_lock_amd32
};
